<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "https://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="https://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="zh-CN" />
<!--[if lt IE 9]><style type="text/css">@font-face { font-family: "JinBuGuoWebMono"; src: url("/d/mono.eot"); }</style><![endif]-->
<style media="all" type="text/css">
@font-face { font-family: "JinBuGuoWebMono"; src: url("http://www.jinbuguo.com/d/mono.ttf") format("truetype"); }
* { font-family: "JinBuGuoWebMono", "Ubuntu Mono", "Consolas", "Menlo", monospace; }
body { margin:10px; }
h1,h2 { text-align:center; background:#ddd; }
h2 { margin: 10px 5%; }
h2#auth_name {  background:#fff; }
dt { margin-top: 0.5em; color:#600; }
pre { background: #edc; }
table { vertical-align:middle; border:solid 1px #111; border-collapse:collapse; margin:4px; background:#fff; }
caption, th { font-weight:bold; }
th, td { border:solid 1px #999; padding:4px; }
</style>
<title>PHP-LDAP 学习笔记 [金步国]</title>
<script> var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?d286c55b63a3c54a1e43d10d4c203e75"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); </script>
</head>

<body>
<h1>PHP-LDAP 学习笔记</h1><h2 id="auth_name">作者：<strong><a href="../index.html">金步国</a></strong></h2>
<hr />
<h3>版权声明</h3>
<p>本文作者是一位开源理念的坚定支持者，所以本文虽然不是软件，但是遵照开源的精神发布。</p>
<ul>
<li>无担保：本文作者不保证作品内容准确无误，亦不承担任何由于使用此文档所导致的损失。</li>
<li>自由使用：任何人都可以自由的<u>阅读/链接/打印</u>此文档，无需任何附加条件。</li>
<li>名誉权：任何人都可以自由的<u>转载/引用/再创作</u>此文档，但必须保留作者署名并注明出处。</li>
</ul>
<h3>其他作品</h3>
<p>本文作者十分愿意与他人分享劳动成果，如果你对我的其他翻译作品或者技术文章有兴趣，可以在如下位置查看现有的作品集：</p>
<ul>
<li><a href="../index.html">金步国作品集</a> [ <a href="../index.html">http://www.jinbuguo.com/</a> ]</li>
</ul>
<h3>联系方式</h3>
<p>由于作者水平有限，因此不能保证作品内容准确无误。如果你发现了作品中的错误(哪怕是错别字也好)，请来信指出，任何提高作品质量的建议我都将虚心接纳。</p>
<ul>
<li>Email(QQ)：70171448在QQ邮箱</li>
</ul>
<hr />




<h2>LDAP简介</h2>
<p><a href="http://zh.wikipedia.org/wiki/%E8%BD%BB%E5%9E%8B%E7%9B%AE%E5%BD%95%E8%AE%BF%E9%97%AE%E5%8D%8F%E8%AE%AE">LDAP</a>(Lightweight Directory Access Protocol)的意思是"轻量级目录访问协议"，是一个用于访问"目录服务器"(Directory Servers)的协议。这里所谓的"目录"是指一种按照树状结构存储信息的数据库。这个概念和硬盘上的目录结构类似，不过LDAP的"根目录"必须是"The world"，并且其一级子目录必须是"countries"。二级目录通常包含有公司(companies)、组织(organisations)、地点(places)等等……相应的三级子目录通常会包含人员(people)、设备(equipment)、文档(documents)等等……</p>
<p>
当你引用硬盘上的文件时，通常是这样的：
<pre>/usr/local/myapp/docs</pre>
正斜杠(/)在这里表示级别分界线，并且从左向右阅读。
</p>
<p>
而在LDAP中则通过"distinguished name"(简称"dn")来表示文件，通常像下面这样：
<pre>cn=John Smith,ou=Accounts,o=My Company,c=US</pre>
逗号(,)在这里表示级别分界线，并且从右向左阅读。上述dn可以理解为：
<pre>
   country = US
   organization = My Company
   organizationalUnit = Accounts
   commonName = John Smith
</pre>
术语对比：
<pre>
dn,entry	目录/文件
attribute	属性
value		值
</pre>
与硬盘目录一样，目录服务器上的目录结构也没有任何限制，这就意味着你必须了解一些服务器的结构信息才能写程序。
</p>
<p>
更多关于LDAP的信息可以在下面两个URL找到：<br>
<a href="https://wiki.mozilla.org/Directory">Mozilla</a><br>
<a href="http://www.openldap.org/">OpenLDAP Project</a>
</p>

<h2>安装与配置</h2>
<p>PHP默认并不启用LDAP支持，PHP的LDAP模块依赖于<a href="ftp://ftp.openldap.org/pub/OpenLDAP/openldap-release/">OpenLDAP</a>或<a href="http://www.bind9.net/download-openldap/">bind9.net</a>提供的客户端LDAP库，你必须在编译的时候使用 --with-ldap[=DIR] 才行，如果你想要SASL支持，那还必须使用 --with-ldap-sasl[=DIR] 选项，而且你的系统中必须有 sasl.h 头文件才行。</p>
<p>LDAP模块的行为受下面的配置指令的影响</p>
<table>
  <tr><th>指令</th><th>数据类型</th><th>作用域</th><th>默认值</th><th>解释</th></tr>
  <tr><td>ldap.max_links</td><td>整数</td><td>PHP_INI_SYSTEM</td><td>-1</td><td>每进程允许的最大LDAP连接数。"-1"表示无限制。</td></tr>
</table>

<h2>编程步骤</h2>
<p>在进行LDAP编程之前，你必须知道目录服务器的下列信息：</p>
<ul>
<li>服务器的IP地址(主机名)以及端口号</li>
<li>服务器的"base dn"(也就是"the world"目录下的子目录，比如："o=My Company,c=US")</li>
<li>访问服务器的用户名(RDN)和密码(许多服务器允许"匿名绑定"来读取信息，但是其他操作必须有密码才行)</li>
</ul>
<p>一般的LDAP编程步骤如下：</p>
<ol>
<li>ldap_connect()    // 连接到LDAP服务器</li>
<li>ldap_bind()       // 匿名登陆或者认证登陆</li>
<li>做一些操作，比如读取、添加、删除等...</li>
<li>ldap_unbind()      // 关闭连接</li>
</ol>

<h2>LDAP常用函数简介</h2>
<dl>

<dt><b><code>resource ldap_connect ([ string $hostname = NULL [, int $port = 389 ]] )</code></b></dt>
<dd>
<p>使用指定的主机名($hostname)和端口($port)连接到LDAP服务器。</p>
<dl>
	<dt><var>$hostname</var></dt>
	<dd>主机名，如果服务器端是 OpenLDAP 2.x.x 或更高版本，那么还可以使用 ldap://hostname/ 格式(URL格式)。<br>
	要使用SSL连接，那么必须确保OpenLDAP和PHP都带有SSL支持，并使用 ldaps://hostname/ 的格式。<br>
	如果使用空格分隔的主机名列表(比如"192.168.0.100:389 192.168.0.101")，那么此函数将依次尝试直到连接成功为止。</dd>
	<dt><var>$port</var></dt>
	<dd>连接的端口(默认为389)。在使用URL格式的时候不需要使用此参数，直接在URL里面指明即可(ldap://hostname:port/)。</dd>
</dl>
<p>连接成功则返回一个LDAP连接标识符，连接失败则返回 FALSE 。<br>
如果服务器端是 OpenLDAP 2.x.x 或更高版本，那么此函数将始终返回一个LDAP连接标识符，而永远不会失败。实际的连接动作将在接下来调用 ldap_* 函数[通常是ldap_bind()]时执行。<br>
如果不带任何参数调用此函数，则返回上一次连接已经打开的连接标识符。</p>
</dd>

<dt><b><code>bool ldap_bind ( resource $link_identifier [, string $bind_rdn = NULL [, string $bind_password = NULL ]] )</code></b></dt>
<dd>
<p>使用指定的RDN($bind_rdn)和密码($bind_password)绑定到LDAP目录。</p>
<dl>
	<dt><var>$link_identifier</var></dt>
	<dd>ldap_connect()函数返回的LDAP连接标识符</dd>
	<dt><var>$bind_rdn<br>$bind_password</var></dt>
	<dd>绑定时使用的RDN(Relative Distinguished Name)和密码。如果未指定则使用匿名认证。</dd>
</dl>
<p>绑定成功返回 TRUE 否则返回 FALSE 。</p>
</dd>

<dt><b><code>string ldap_error ( resource $link_identifier )</code></b></dt>
<dd>
<p>检索LDAP操作的错误信息。</p>
<dl>
	<dt><var>$link_identifier</var></dt>
	<dd>ldap_connect()函数返回的LDAP连接标识符</dd>
</dl>
<p>返回指定的连接标识符上最近一次LDAP操作的错误信息。</p>
</dd>

<dt><b><code>bool ldap_get_option ( resource $link_identifier , int $option , mixed &$retval )<br>bool ldap_set_option ( resource $link_identifier , int $option , mixed $newval )</code></b></dt>
<dd>
<p>获取/设置LDAP选项的值。</p>
<dl>
	<dt><var>$link_identifier</var></dt>
	<dd>ldap_connect()函数返回的LDAP连接标识符</dd>
	<dt><var>$option</var></dt>
	<dd>LDAP选项名称，主要的选项及其数据类型如下：</dd>
	<dd><dl>
		<dt>LDAP_OPT_DEREF(int)</dt>
		<dd>搜索的时候如何处理别名，取值范围如下：LDAP_DEREF_NEVER(0,默认值), LDAP_DEREF_SEARCHING(1), LDAP_DEREF_FINDING(2), LDAP_DEREF_ALWAYS(3)</dd>
		<dt>LDAP_OPT_NETWORK_TIMEOUT(int)</dt>
		<dd>网络超时秒数，LDAP_NO_LIMIT(0,默认值)表示永不超时。</dd>
		<dt>LDAP_OPT_PROTOCOL_VERSION(int)</dt>
		<dd>指定使用的LDAP协议版本，取值范围如下：LDAP_VERSION2(2,默认值), LDAP_VERSION3 (3)。</dd>
		<dt>LDAP_OPT_REFERRALS(bool)</dt>
		<dd>LDAP库是否自动追随LDAP服务器返回的引用，取值范围如下：TRUE(1,默认值), FALSE(0)。</dd>
	</dl></dd>
	<dt><var>&$retval</var></dt>
	<dd>接受选项值的变量</dd>
	<dt><var>$newval</var></dt>
	<dd>选项的新值</dd>
</dl>
<p>获取/设置成功返回 TRUE 否则返回 FALSE 。</p>
</dd>

<dt><b><code>bool ldap_add    ( resource $link_identifier , string $dn , array $entry )<br>bool ldap_delete ( resource $link_identifier , string $dn )</code></b></dt>
<dd>
<p>在LDAP目录中添加/删除一个项。</p>
<dl>
	<dt><var>$link_identifier</var></dt>
	<dd>ldap_connect()函数返回的LDAP连接标识符</dd>
	<dt><var>$dn</var></dt>
	<dd>将要添加/删除的项目名称(distinguished name)。</dd>
	<dt><var>$entry</var></dt>
	<dd>指定新项内容的关联数组(attribute=&gt;value)。如果value也是一个数组，那么必须是从0开始的索引数组。例如：
<pre>
$entry["bool"]='TRUE';
$entry["int"]='-3';
$entry["mail"]='jonj@example.com';
$entree["tel"][0] = "2222222";
$entree["tel"][1] = "4444444";
</pre>
	</dd>
</dl>
<p>添加/删除成功返回 TRUE 否则返回 FALSE 。</p>
</dd>

<dt><b><code>mixed ldap_compare ( resource $link_identifier , string $dn , string $attribute , string $value )</code></b></dt>
<dd>
<p>检查LDAP目录项$dn的$attribute属性值与给定的$value是否相同。注意：不能用来比较二进制数据！</p>
<dl>
	<dt><var>$link_identifier</var></dt>
	<dd>ldap_connect()函数返回的LDAP连接标识符</dd>
	<dt><var>$dn</var></dt>
	<dd>LDAP项的名称(distinguished name)</dd>
	<dt><var>$attribute</var></dt>
	<dd>属性名</dd>
	<dt><var>$value</var></dt>
	<dd>给定的值</dd>
</dl>
<p>相同返回 TRUE ，不同返回 FALSE ，遇见错误返回 -1 </p>
</dd>

<dt><b><code>resource ldap_read   ( resource $link_identifier , string $base_dn , string $filter [, array $attributes [, int $attrsonly [, int $sizelimit [, int $timelimit [, int $deref ]]]]] )<br>resource ldap_list   ( resource $link_identifier , string $base_dn , string $filter [, array $attributes [, int $attrsonly [, int $sizelimit [, int $timelimit [, int $deref ]]]]] )<br>resource ldap_search ( resource $link_identifier , string $base_dn , string $filter [, array $attributes [, int $attrsonly [, int $sizelimit [, int $timelimit [, int $deref ]]]]] )</code></b></dt>
<dd>
<p>
使用指定的过滤器搜索LDAP目录。三个函数的不同之处在于搜索范围不同：<br>
read仅搜索$base_dn本身(LDAP_SCOPE_BASE)，相当于从目录中读取一个条目(entry)。<br>
list仅搜索$base_dn的下一级对象(LDAP_SCOPE_ONELEVEL)，不包含本身，相当于"ls"命令。<br>
search搜索$base_dn本身及其所有子对象(LDAP_SCOPE_SUBTREE)，相当于搜索整个目录树。
</p>
<dl>
	<dt><var>$link_identifier</var></dt>
	<dd>ldap_connect()函数返回的LDAP连接标识符</dd>
	<dt><var>$base_dn</var></dt>
	<dd>将要被搜索的目录的DN</dd>
	<dt><var>$filter</var></dt>
	<dd>搜索过滤器。比如"(objectClass=*)"表示搜索所有条目(对于read函数则表示所有属性)。<br>其语法规则在<a href="http://www.ietf.org/rfc/rfc2254.txt">RFC2254</a>中描述，例如："(|(sn=$person*)(givenname=$person*))"</dd>
	<dt><var>$attributes</var></dt>
	<dd>需要检索的属性名称数组，比如：array("mail", "sn", "cn")。<br>注意：不管有没有指定，"dn"属性的值总会被返回。<br>如果不指定此参数，那么将返回所有的属性值。</dd>
	<dt><var>$attrsonly</var></dt>
	<dd>默认值"0"表示同时获取属性的类型(attribute type)和属性的值(attribute value)。<br>非默认值"1"表示仅获取属性的类型(attribute type)。</dd>
	<dt><var>$sizelimit</var></dt>
	<dd>限定最多返回多少条记录。默认值"0"表示没有限制。注意：这个参数不能超越服务器的限制。</dd>
	<dt><var>$timelimit</var></dt>
	<dd>限定搜索操作最大允许进行多少秒。默认值"0"表示没有限制。注意：这个参数不能超越服务器的限制。</dd>
	<dt><var>$deref</var></dt>
	<dd>指定搜索的时候如何处理别名。取值范围如下：</dd>
	<dd><dl>
		<dt>LDAP_DEREF_NEVER(默认)</dt>
		<dd>在搜索中或者查找那基础对象时做不复引用别名</dd>
		<dt>LDAP_DEREF_SEARCHING</dt>
		<dd>在基础对象的附属的搜索中而不是查找那基础对象时做复引用别名</dd>
		<dt>LDAP_DEREF_FINDING</dt>
		<dd>在基础对象而不是其附属的搜索中做复引用别名</dd>
		<dt>LDAP_DEREF_ALWAYS</dt>
		<dd>在搜索中或者查找那基础对象时做都复引用别名</dd>
	</dl></dd>
</dl>
<p>成功返回一个结果集的资源描述符，通常被其他函数以$result_identifier引用，失败返回FALSE</p>
</dd>

<dt><b><code>array ldap_get_entries  ( resource $link_identifier  , resource $result_identifier  )</code></b></dt>
<dd>
<p>返回前一次搜索操作的结果集，包括每一项的属性和属性值。</p>
<dl>
	<dt><var>$link_identifier</var></dt>
	<dd>ldap_connect()函数返回的LDAP连接标识符</dd>
	<dt><var>$result_identifier</var></dt>
	<dd>ldap_read/ldap_list/ldap_search函数返回的结果集的资源描述符。</dd>
</dl>
<p>
成功返回一个包含结果集的多维数组，失败返回FALSE。<br>
注意：结果集数组的属性索引将被转换成小写字母(对于LDAP来说属性是大小写无关的)。<br>
结果集数组的结构如下所示：
<pre>
return_value["count"] = 结果集中的条目总数(包含第零项)
return_value[0] : 对结果集中第零项(一般不同于后面的常规项)的详细信息的引用

return_value[i]["dn"] =  结果集中第 i 个条目的 DN
return_value[i]["count"] = 结果集中第 i 个条目的属性数量
return_value[i][j] = 结果集中第 i 个条目的第 j 个属性

return_value[i]["attribute"]["count"] = 结果集中第 i 个条目的"attribute"属性的值的数量
return_value[i]["attribute"][j] = 结果集中第 i 个条目的"attribute"属性的第 j 个值
</pre>
</p>
</dd>

<dt><b><code>bool ldap_unbind ( resource $link_identifier )</code></b></dt>
<dd>
<p>解除绑定，也就是关闭LDAP连接。</p>
<dl>
	<dt><var>$link_identifier</var></dt>
	<dd>ldap_connect()函数返回的LDAP连接标识符</dd>
</dl>
<p>成功返回 TRUE 否则返回 FALSE 。</p>
</dd>

</dl>

<hr />

</body></html>